
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/inotify.h>
#include <unistd.h>
#include <fcntl.h>
#include <pthread.h>
#include <sys/epoll.h>
#include <sys/file.h>
#include "user_proc.h"

/**********  结构定义    **********/
typedef struct {
    int     inotify_fd;
    char    file_path[FILE_PATH_MAX_LEN];
    int     (*update_proc_file)(FILE *fd, void *user_pram);
    void    *user_pram;
} user_proc_param_t;


/**********  全局变量    **********/
// 初始化标志
int user_proc_init_flag;
// 线程运行控制
int proc_thread_running;
// proc用户参数全局变量
user_proc_param_t user_proc_param[PROC_FILE_MAX_NUM];
// 当前已创建proc文件数量
unsigned int proc_file_num;
// epoll全局变量
struct epoll_event ep_events[PROC_FILE_MAX_NUM];
int epoll_fd;


/**********  帮助函数    **********/
int file_is_exist(char *path)
{
    int file_exist = 0;
    // 通过打开文件来判读文件是否存在
    FILE *fp = fopen(path, "r");
    file_exist = (fp == NULL) ? 0 : 1;
    if (file_exist) {
        fclose(fp);
    }
    return file_exist;
}
int create_file(char *path)
{
    // 创建文件分为两个步骤：
    // 1. 创建文件所在目录
    // 2. 在文件所在目录创建文件

    char shell_cmd[256] = {0};
    char mkdir_path[128] = {0};
    // 根据传入的文件绝对路径获取文件所在目录（如"/root/test/a.txt" 可得 "/root/test"）
    strncpy(mkdir_path, path, strrchr(path, '/') - path);
        // printf("mkdir_path: %s\n", mkdir_path);
    // 使用mkdir -p 创建目录（-p参数：如果中途目录不存在，则全部创建）
    snprintf(shell_cmd, sizeof(shell_cmd), "mkdir -p %s", mkdir_path);
    if (system(shell_cmd) < 0) {
        printf("run shell cmd:%s fial.\n", shell_cmd);
        return -1;
    }
    // 按照绝对路径创建文件
    snprintf(shell_cmd, sizeof(shell_cmd), "touch %s", path);
    if (system(shell_cmd) < 0) {
        printf("run shell cmd:%s fial.\n", shell_cmd);        
        return -2;
    }
    return 0;
}
int delete_file(char *path)
{
    if (!file_is_exist(path)) {
        // 如果文件不存在，退出
        return -1;
    }

    // rm /root/a.txt
    char shell_cmd[512] = {0};
    snprintf(shell_cmd, sizeof(shell_cmd), "rm %s", path);
    
    if (system(shell_cmd) < 0) {
        printf("run shell cmd:%s fial.\n", shell_cmd);        
        return -1;
    }

    return 0;
}


/**********  user proc 工作线程    **********/
void *listen_proc_file_tfunc(void *arg)
{
    // 循环监听事件
    struct inotify_event *event;
    char buffer[1024];
    ssize_t len;

    while (proc_thread_running) {
        // 使用epoll监控可读套接字
        // printf("epoll_wait...\n");
        int nfds = epoll_wait(epoll_fd, ep_events, PROC_FILE_MAX_NUM, -1);
        if (nfds < 0) {
            perror("epoll_wait");
            continue;
        }
        for (int i = 0; i < nfds; i++) {

            int handle_fd = ep_events[i].data.fd;
            for (int j = 0; j < PROC_FILE_MAX_NUM; j++) {
                if (user_proc_param[j].inotify_fd == handle_fd) {
                    int len = read(user_proc_param[j].inotify_fd, buffer, sizeof(buffer));
                    for (event = (struct inotify_event *)buffer; event < (struct inotify_event *)&buffer[len]; event++) {

                        if (event->mask & IN_ATTRIB) {              
                            // printf("File IN_ATTRIB: %s\n", event->name);
                            // 打开文件
                            FILE *fs = fopen(user_proc_param[j].file_path, "r+");
                            if (fs == NULL) {
                                printf("Error open file[%s]\n", user_proc_param[j].file_path);
                                continue;
                            }
                            // 获取描述符，并加锁准备写数据
                            int fd = fileno(fs);
                            if(0 != flock(fd, LOCK_EX)){
                                printf("flock(descriptor, LOCK_EX) fail.\n");
                            }
                            // 清空文件
                            if (ftruncate(fd, 0) < 0) {
                                printf("ftruncate() fail.\n");
                            }
                            // 将文件指针重置到文件开始位置
                            fseek(fs, 0, SEEK_SET);              
                            // 调用 用户回调 刷新文件
                            user_proc_param[j].update_proc_file(fs, user_proc_param[j].user_pram);
                            // 文件写完，释放锁
                            if(0 != flock(fd, LOCK_UN)){
                                printf("flock(descriptor, LOCK_UN) fail.\n");
                            }
                            fclose(fs);
                        } 
                    }
                    // 匹配到fd后，停止后续遍历
                    break;
                }
            }
        }
    }
    
    printf("listen_proc_file_tfunc exit.\n");
    return NULL;
}

int init_proc_file(char *path, int (*update_proc_file)(FILE *fd, void *user_pram), void *user_pram)
{
    int ret = 0;

    if (proc_file_num >= PROC_FILE_MAX_NUM) {
        printf("proc_file_num is full(max: %d)!\n", PROC_FILE_MAX_NUM);
        ret = -1;
        goto exit0;
    }

    if (strlen(path) > FILE_PATH_MAX_LEN) {
        printf("path is too long(max len: %d)!\n", FILE_PATH_MAX_LEN);
        ret = -1;
        goto exit0;
    }

    // 创建文件
    if (create_file(path) < 0) {
        printf("create_file(%s) fail!\n", path);
        ret = -2;
        goto exit0;
    }

    // 应用层proc初始化，初次添加proc文件时启线程
    if (user_proc_init_flag == 0) {
        epoll_fd = epoll_create(1);
        proc_thread_running = 1;
        user_proc_init_flag = 1;
        pthread_t listen_proc_file_tid;
        pthread_create(&listen_proc_file_tid, NULL, listen_proc_file_tfunc, NULL);
        pthread_detach(listen_proc_file_tid);
        usleep(10000);
    }

    // 创建一个inotify实例
    int inotify_fd = inotify_init();
    if (inotify_fd == -1) {
        perror("inotify_init");
        ret = -3;
        goto exit1;
    }
    // 设置inotify事件
    // int event_mask = IN_OPEN | IN_CLOSE_WRITE | IN_CLOSE_NOWRITE;
    // int event_mask = IN_OPEN | IN_ACCESS;    // 不能监听OPEN，会陷入无尽递归
    int event_mask = IN_ATTRIB; // 文件权限被修改
    if (inotify_add_watch(inotify_fd, path, event_mask) == -1) {
        perror("inotify_add_watch");
        ret = -3;
        goto exit2;
    }

    // 添加进epoll监听
    struct epoll_event ep_event;
    ep_event.data.fd = inotify_fd;
    ep_event.events  = EPOLLIN; // EPOLLIN? EPOLLOUT?
    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, inotify_fd, &ep_event) < 0) {
        ret = -4;
        goto exit2;
    }

    // 记录本次参数
    memset(&(user_proc_param[proc_file_num]), 0, sizeof(user_proc_param_t));
    user_proc_param[proc_file_num].inotify_fd       = inotify_fd;
    memcpy(user_proc_param[proc_file_num].file_path, path, strlen(path));
    user_proc_param[proc_file_num].update_proc_file = update_proc_file;
    user_proc_param[proc_file_num].user_pram        = user_pram;

    proc_file_num++;

    return inotify_fd;

exit2:
    close(inotify_fd);
exit1:
    delete_file(path);
exit0:
    return ret;
}

void deinit_proc_file(int del_fd)
{
    user_proc_param_t *del_proc = NULL;
    int i = 0, j = 0;
    // 找到要删除的proc
    for (i = 0; i < PROC_FILE_MAX_NUM; i++) {
        if (user_proc_param[i].inotify_fd == del_fd) {
            del_proc = &user_proc_param[i];
            break;
        }
    }
    if (del_proc == NULL) {
        return;
    }

    // 删除fd（ path <-> del_fd ）
    if (epoll_ctl(epoll_fd, EPOLL_CTL_DEL, del_fd, NULL) < 0) {
        printf("epoll_ctl(delete, %s) fail.\n", del_proc->file_path);
    }

    // 删除文件
    delete_file(del_proc->file_path);

    // 清理
    memset(del_proc, 0, sizeof(user_proc_param_t));

    // 前移
    for (j = i; j < (PROC_FILE_MAX_NUM - 1); j++) {
        user_proc_param[j] = user_proc_param[j+1];
    }

    // proc文件计数减少
    proc_file_num--;

    // 无proc文件，关闭线程
    if (proc_file_num == 0) {
        proc_thread_running = 0;
        user_proc_init_flag = 0;
        close(epoll_fd);
    }
    return;
}
